package com.polaris.live.utils.zego.core

import android.app.Activity
import androidx.core.os.bundleOf
import com.blankj.utilcode.util.AppUtils
import com.blankj.utilcode.util.NetworkUtils
import com.blankj.utilcode.util.Utils
import com.google.firebase.analytics.ktx.analytics
import com.google.firebase.ktx.Firebase
import com.polaris.live.App
import com.polaris.live.common.constant.ServerConfig
import com.polaris.live.common.manager.CoroutineScopeManager
import com.polaris.live.common.manager.withDeferred
import com.polaris.live.common.manager.withRetry
import com.polaris.live.common.util.LogExt
import com.polaris.live.common.util.StructUtils
import com.polaris.live.constant.EventConst
import com.polaris.live.resp.event.LoginZegoEvent
import com.polaris.live.utils.file.FileUtils
import com.polaris.live.utils.postEvent
import com.polaris.live.utils.zego.LiveEngineManage
import com.polaris.live.utils.zego.OnWithLoginListener
import im.zego.zegoexpress.ZegoExpressEngine
import im.zego.zegoexpress.callback.IZegoEventHandler
import im.zego.zegoexpress.constants.ZegoAudioDeviceMode
import im.zego.zegoexpress.constants.ZegoRoomStateChangedReason
import im.zego.zegoexpress.constants.ZegoScenario
import im.zego.zegoexpress.constants.ZegoVideoBufferType
import im.zego.zegoexpress.entity.ZegoCustomVideoProcessConfig
import im.zego.zegoexpress.entity.ZegoEngineConfig
import im.zego.zegoexpress.entity.ZegoEngineProfile
import im.zego.zegoexpress.entity.ZegoLogConfig
import im.zego.zegoexpress.entity.ZegoRoomConfig
import im.zego.zegoexpress.entity.ZegoUser
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import org.greenrobot.eventbus.EventBus
import org.json.JSONObject

/**
 * EngineHandler
 *
 * @author Created by lucas on 2023/11/25 19:36
 * @since 1.0.0
 */
class EngineHandler(
    private val appId: Long,
    eventHandlerHelper: EventHandlerHelper,
    gameEventHandlerHelper: GameEventHandlerHelper
) {

    private val engine: ZegoExpressEngine

    private val gameHandler: MiniGameEngineHandler

    private val tokenHandler: ZegoTokenHandler = ZegoTokenHandler()

    private var isConnected: Boolean = false

    @Volatile
    private var isLoginRoom: Boolean = false

    private var currentRoomId: String? = null

    private val onWithLoginListeners = StructUtils.newSaleSet<OnWithLoginListener>()

    private var loginJob: Job? = null//登录job
    private var gameLoginJob: Job? = null//游戏登录job

    @Volatile
    private var lastEnableAudioHash: Int? = null//上次开启音频的hash

    private val eventHandler = object : IZegoEventHandler() {

        /**
         * token过期 提前30s通知
         */
        override fun onRoomTokenWillExpire(roomID: String?, remainTimeInSecond: Int) {
            reToken(1000)
        }

        /**
         * 房间状态改变
         */
        override fun onRoomStateChanged(
            roomID: String?,
            reason: ZegoRoomStateChangedReason?,
            errorCode: Int,
            extendedData: JSONObject?,
        ) {
            try {
                Firebase.analytics.postEvent(
                    EventConst.ZEGO_ROOM_STATE_CHANGED, bundleOf(
                        "roomId" to roomID,
                        "reason" to reason?.name,
                        "errorCode" to errorCode,
                        "extendedData" to extendedData?.toString(),
                        "isConnected" to isConnected,
                        "isLoginRoom" to isLoginRoom,
                        "zegoToken" to tokenHandler.getCacheToken(),
                        "foreground" to AppUtils.isAppForeground()
                    )
                )
            } catch (_: Throwable) {
            }

            //因为只登录自己的房间，所以这里不判断roomId
            when (reason) {
                ZegoRoomStateChangedReason.LOGINED, ZegoRoomStateChangedReason.RECONNECTED -> {
                    isLoginRoom = true

                    synchronized(this@EngineHandler) {
                        val iterator = onWithLoginListeners.iterator()
                        while (iterator.hasNext()) {
                            iterator.next().onSuccess()

                            iterator.remove()
                        }
                    }

                }

                ZegoRoomStateChangedReason.LOGINING, ZegoRoomStateChangedReason.RECONNECTING, ZegoRoomStateChangedReason.LOGOUT -> {
                    isLoginRoom = false
                }

                ZegoRoomStateChangedReason.LOGIN_FAILED, ZegoRoomStateChangedReason.RECONNECT_FAILED, ZegoRoomStateChangedReason.KICK_OUT -> {
                    if (reason == ZegoRoomStateChangedReason.LOGIN_FAILED && errorCode == LOGIN_REPEATEDLY) {
                        return
                    }

                    //登录失败，需要重新登录
                    isLoginRoom = false

                    if (reason == ZegoRoomStateChangedReason.KICK_OUT && errorCode == LOGIN_KICK) {
                        CoroutineScopeManager.defaultScope.launch {
                            tokenHandler.checkToken()

                            reLogin(1000)
                        }
                    } else {
                        reLogin(1000)
                    }
                }

                ZegoRoomStateChangedReason.LOGOUT_FAILED, null -> {
                    //什么都不做
                    isLoginRoom = false
                }
            }
        }
    }

    private val gameEventHandler = object : ZegoGameEngineHandler() {

        // token 过期前 30s 触发回调，通常配合方法 updateToken 函数使用，回调函数无参
        override fun onTokenWillExpire() {
            reGameToken()
        }
    }

    init {
        //非线上启用日志
        if (!ServerConfig.isProdServer()) {
            try {
                val logConfig = ZegoLogConfig()
                // 设置日志存储路径
                logConfig.logPath = FileUtils.getZegoLogCacheDir()

                // 设置单个日志文件大小上限
                logConfig.logSize =
                    FileUtils.convertFileSize(10f, FileUtils.SIZE_MB, FileUtils.SIZE_BYTE).toLong()
                ZegoExpressEngine.setLogConfig(logConfig)
            } catch (t: Throwable) {
                LogExt.logE(LiveEngineManage.TAG, "Zego open log failure", t)
            }
        }

        val profile = ZegoEngineProfile().apply {
            appID = appId
            scenario = ZegoScenario.HIGH_QUALITY_VIDEO_CALL // 指定使用直播场景
            application = App.instance
        }

        eventHandlerHelper.addEventHandler(eventHandler)

        engine = ZegoExpressEngine.createEngine(profile, eventHandlerHelper)

        //设置既构重连时间，这里设置为6小时，理论上太长是无意义的
        val configEngine = ZegoEngineConfig().apply {
            advancedConfig["room_retry_time"] = "21600"
            advancedConfig["av_retry_time"] = "21600"
        }
        ZegoExpressEngine.setEngineConfig(configEngine, App.instance)

        // 设置自定义视频采集
        val configVideo = ZegoCustomVideoProcessConfig().apply {
            bufferType = ZegoVideoBufferType.GL_TEXTURE_2D
        }
        engine.enableCustomVideoProcessing(true, configVideo)
        //这个模式下不会自动占用麦克风，这种模式下会使用软件消除回声，使得耳返延迟上升
        engine.setAudioDeviceMode(ZegoAudioDeviceMode.COMMUNICATION3)

        systemMonitor()

        gameHandler = MiniGameEngineHandler(appId, tokenHandler)
        gameEventHandlerHelper.addEventHandler(gameEventHandler)
    }

    private fun systemMonitor() {
        //监听网络状态
        NetworkUtils.registerNetworkStatusChangedListener(object :
            NetworkUtils.OnNetworkStatusChangedListener {
            override fun onDisconnected() {
            }

            override fun onConnected(networkType: NetworkUtils.NetworkType?) {
                //尝试登录一次
                if (!isLoginRoom) {
                    reLogin(1000)
                }
            }
        })
        AppUtils.registerAppStatusChangedListener(object : Utils.OnAppStatusChangedListener {
            override fun onBackground(activity: Activity?) {
            }

            override fun onForeground(activity: Activity?) {
                //尝试登录一次
                if (!isLoginRoom) {
                    reLogin(1000)
                }
            }
        })
    }

    /**
     * 获取zego的引擎
     */
    fun getEngine(): ZegoExpressEngine {
        return engine
    }

    fun getGameEngineHandler(): MiniGameEngineHandler {
        return gameHandler
    }

    /**
     * 连接到zego的服务器
     */
    fun connect(roomId: String) {
        if (isConnected) {
            return
        }

        currentRoomId = roomId

        isConnected = true

        reLogin()

        gameHandler.initEngIne()
    }

    /**
     * 断开连接，比如退出登录时
     */
    fun disconnect() {
        if (!isConnected) {
            return
        }

        isConnected = false

        loginJob?.cancel()
        loginJob = null

        gameLoginJob?.cancel()
        gameLoginJob = null

        engine.logoutRoom()
        tokenHandler.releaseToken()
        gameHandler.unInit()

        currentRoomId = null
    }

    /**
     * 重试登录
     */
    private fun reLogin(delay: Long = 0L) {
        //如果是非连接状态说明是主动断开的连接
        if (!isConnected) {
            return
        }
        loginJob?.cancel()
        loginJob = CoroutineScopeManager.defaultScope.launch {
            if (delay > 0) {
                delay(delay)
            }

            try {
                //如果失败无限重试，并且每次重试间隔1s
                withRetry(Int.MAX_VALUE, { 1000L }) {
                    invokeLoginRoom(currentRoomId!!, tokenHandler.getToken(zegoErrorCode))
                }
            } finally {
                if (loginJob == coroutineContext[Job]) {
                    loginJob = null
                }
            }
        }
    }

    /**
     * 刷新token
     */
    private fun reToken(delay: Long = 0L) {
        //如果是非连接状态说明是主动断开的连接
        if (!isConnected) {
            return
        }
        loginJob?.cancel()
        loginJob = CoroutineScopeManager.defaultScope.launch {
            if (delay > 0) {
                delay(delay)
            }
            try {
                //如果失败无限重试，并且每次重试间隔1s
                withRetry(Int.MAX_VALUE, { 1000L }) {
                    engine.renewToken(
                        currentRoomId!!,
                        tokenHandler.getToken(zegoErrorCode)
                    ) //刷新token
                }
            } finally {
                if (loginJob == coroutineContext[Job]) {
                    loginJob = null
                }
            }
        }
    }

    private fun reGameToken() {
        gameLoginJob?.cancel()
        gameLoginJob = CoroutineScopeManager.defaultScope.launch {
            try {
                //如果失败无限重试，并且每次重试间隔1s
                withRetry(Int.MAX_VALUE, { 1000L }) {
                    gameHandler.updateToken(tokenHandler.getToken(zegoErrorCode))
                }
            } finally {
                if (gameLoginJob == coroutineContext[Job]) {
                    gameLoginJob = null
                }
            }
        }
    }

    //Token错误码 特殊处理
    private var zegoErrorCode: Int? = null

    /**
     * 登录
     */
    private suspend fun invokeLoginRoom(roomId: String, token: String) {
        withDeferred {
            val user = ZegoUser(roomId)
            val roomConfig = ZegoRoomConfig().apply {
                this.token = token
                this.isUserStatusNotify = true

            }
            engine.loginRoom(roomId, user, roomConfig) { errorCode, extData ->
                if (errorCode == LOGIN_SUCCESS || errorCode == LOGIN_REPEATEDLY) {
                    LogExt.logI(LiveEngineManage.TAG, "Room login success $errorCode")
                    zegoErrorCode = null
                    complete(Unit)

                    if (errorCode == LOGIN_SUCCESS) {
                        EventBus.getDefault().post(LoginZegoEvent())
                    }
                } else {
                    LogExt.logI(LiveEngineManage.TAG) {
                        "Room login failure $errorCode, $extData"
                    }
                    if (errorCode in ZEGO_TOKEN_ERROR) {
                        zegoErrorCode = errorCode
                    }
                    completeExceptionally(ZegoException(errorCode, extData, "Room login failure"))
                }
            }
        }
    }

    /**
     * 是否已经连接
     *
     * 这个代表当前是否期望连接
     */
    fun isConnected(): Boolean {
        return isConnected
    }

    /**
     * 是否已经登录房间
     *
     * 这个表示当前真实的状态
     */
    fun isLoginRoom(): Boolean {
        return isLoginRoom
    }

    /**
     * 开启或关闭音频采集
     */
    fun enableAudioCaptureDevice(hashId: Int, enable: Boolean) {
        if (enable) {
            engine.enableAudioCaptureDevice(true)
            lastEnableAudioHash = hashId
        } else {
            if (lastEnableAudioHash == hashId) {
                engine.enableAudioCaptureDevice(false)
                lastEnableAudioHash = null
            }
        }
    }

    /**
     * 附加登录监听
     */
    fun attachLoginLister(l: OnWithLoginListener) {
        if (isLoginRoom) {
            l.onSuccess()
        } else {
            if (!NetworkUtils.isConnected()) {
                l.onFailure(Throwable("network is disconnected"))

                Firebase.analytics.postEvent(
                    EventConst.ZEGO_LOGIN_ERROR, bundleOf(
                        "errorMessage" to "network is disconnected"
                    )
                )
            } else {
                onWithLoginListeners.add(l)
            }
        }
    }

    /**
     * 移除登录监听
     */
    fun detachedLoginLister(l: OnWithLoginListener) {
        onWithLoginListeners.remove(l)
    }

    /**
     * 上报日志
     */
    fun uploadLog() {
        try {
            engine.uploadLog()
        } catch (_: Throwable) {
        }
    }

    companion object {

        /**
         * 房间登录成功
         */
        const val LOGIN_SUCCESS = 0

        /**
         * 重复加入房间
         */
        const val LOGIN_REPEATEDLY = 1002001

        /**
         * 被踢出房间
         */
        const val LOGIN_KICK = 1002050

        /**
         * Token问题
         */
        private val ZEGO_TOKEN_ERROR = setOf(
            1002002,
            1002033,
            1002056,
            1002067,
            1002074,
            1002075,
            1002076,
            1002077,
            1002078,
            1002079,
            1002080,
            1002082,
            1003072,
            1004003,
            1017003,
            1017009,
            1019006,
            1019007,
        )
    }
}
